diff --git a/src/Simulation/CSharpGeneration.Tests/Circuits/SubDirectory/HelloOther.qs b/src/Simulation/CSharpGeneration.Tests/Circuits/SubDirectory/HelloOther.qs new file mode 100644 index 00000000000..3b7cdc8cf2d --- /dev/null +++ b/src/Simulation/CSharpGeneration.Tests/Circuits/SubDirectory/HelloOther.qs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Tests.SubDirectory +{ + + operation HelloOther (n : Int) : Int + { + let r = n + 1; + + return r; + } + +} + + diff --git a/src/Simulation/CSharpGeneration.Tests/Circuits/SubDirectory/HelloWorld.qs b/src/Simulation/CSharpGeneration.Tests/Circuits/SubDirectory/HelloWorld.qs new file mode 100644 index 00000000000..ba40ec64cc5 --- /dev/null +++ b/src/Simulation/CSharpGeneration.Tests/Circuits/SubDirectory/HelloWorld.qs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Tests.SubDirectory +{ + + operation HelloWorld (n : Int) : Int + { + let r = n + 1; + + return r; + } + +} + + diff --git a/src/Simulation/CSharpGeneration.Tests/SimulationCodeTests.fs b/src/Simulation/CSharpGeneration.Tests/SimulationCodeTests.fs index dfe7a2e216d..433b86fa6ed 100644 --- a/src/Simulation/CSharpGeneration.Tests/SimulationCodeTests.fs +++ b/src/Simulation/CSharpGeneration.Tests/SimulationCodeTests.fs @@ -108,10 +108,10 @@ namespace N1 |> failwith let functorGenSuccessful = CodeGeneration.GenerateFunctorSpecializations(compilation, &compilation) // todo: we might want to raise an error here if the functor generation fails (which will be the case for incorrect code) - compilation.Namespaces + compilation with | e -> sprintf "compilation threw exception: \n%s" e.Message |> failwith // should never happen (all exceptions are caught by the compiler) - let syntaxTree = parse [ (Path.Combine("Circuits", "Intrinsic.qs")); (Path.Combine("Circuits", "CodegenTests.qs")) ] + let syntaxTree = parse [ (Path.Combine("Circuits", "Intrinsic.qs")); (Path.Combine("Circuits", "CodegenTests.qs")) ] |> (fun compilation -> compilation.Namespaces) let globalContext = CodegenContext.Create syntaxTree @@ -225,9 +225,9 @@ namespace N1 expected |> (fun s -> s.Replace("%%%", fullPath |> HttpUtility.JavaScriptStringEncode |> escapeCSharpString)) |> (fun s -> s.Replace("%%", fullPath |> escapeCSharpString)) - let tree = parse [Path.Combine ("Circuits", "Intrinsic.qs"); fileName] + let compilation = parse [Path.Combine ("Circuits", "Intrinsic.qs"); fileName] let actual = - CodegenContext.Create (tree, ImmutableDictionary.Empty) + CodegenContext.Create (compilation, ImmutableDictionary.Empty) |> generate (Path.GetFullPath fileName) Assert.Equal(expected |> clearFormatting, actual |> clearFormatting) @@ -248,6 +248,58 @@ namespace N1 List.zip expected actual |> List.iter Assert.Equal<'Z> + [] + let ``testGeneratedFileNames`` () = + + let outputDir = "output" |> Path.GetFullPath + let outputDirSrc = Path.Combine(outputDir, "src") + + let intrinsic = Path.Combine(outputDirSrc, "Intrinsic.g.cs") + let helloWorld = Path.Combine(outputDirSrc, "HelloWorld.g.cs") + let helloWorld1 = Path.Combine(outputDirSrc, "HelloWorld.g1.cs") + let helloOther = Path.Combine(outputDirSrc, "HelloOther.g.cs") + + let deleteFile filePath = + if File.Exists filePath then + File.Delete filePath + + intrinsic |> deleteFile + helloWorld |> deleteFile + helloWorld1 |> deleteFile + helloOther |> deleteFile + + let compilation = parse [ + Path.Combine("Circuits", "Intrinsic.qs") + Path.Combine("Circuits", "HelloWorld.qs") + Path.Combine("Circuits", "SubDirectory", "HelloWorld.qs") + Path.Combine("Circuits", "SubDirectory", "HelloOther.qs") + ] + let transformed = ref { Namespaces = ImmutableArray.Empty; EntryPoints = ImmutableArray.Empty } + let rewriteStep = Emitter() :> IRewriteStep + rewriteStep.AssemblyConstants.Add(AssemblyConstants.OutputPath, outputDir) + rewriteStep.Transformation(compilation, transformed) |> ignore + + let mutable allFilesFound = true + let checkAndDeleteFile filePath = + if File.Exists filePath then + File.Delete filePath + else + allFilesFound <- false + + intrinsic |> checkAndDeleteFile + helloWorld |> checkAndDeleteFile + helloWorld1 |> checkAndDeleteFile + helloOther |> checkAndDeleteFile + + let deleteDir dirPath = + if Directory.Exists dirPath && Directory.EnumerateFileSystemEntries dirPath |> Seq.isEmpty then + Directory.Delete dirPath + + outputDirSrc |> deleteDir + outputDir |> deleteDir + + Assert.True allFilesFound + [] let ``tupleBaseClassName test`` () = let testOne (_, udt) expected = diff --git a/src/Simulation/CSharpGeneration.Tests/Tests.CSharpGeneration.fsproj b/src/Simulation/CSharpGeneration.Tests/Tests.CSharpGeneration.fsproj index 0a421156c67..f19303756a8 100644 --- a/src/Simulation/CSharpGeneration.Tests/Tests.CSharpGeneration.fsproj +++ b/src/Simulation/CSharpGeneration.Tests/Tests.CSharpGeneration.fsproj @@ -8,6 +8,12 @@ + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/Simulation/CSharpGeneration/RewriteStep.fs b/src/Simulation/CSharpGeneration/RewriteStep.fs index 10ea2242f05..cd8527bddfa 100644 --- a/src/Simulation/CSharpGeneration/RewriteStep.fs +++ b/src/Simulation/CSharpGeneration/RewriteStep.fs @@ -18,6 +18,31 @@ type Emitter() = let _AssemblyConstants = new Dictionary<_, _>() + let _FileNamesGenerated = new HashSet(); + + [] + let _EnumerationLimit = 100; + + member private this.WriteFile (fileId : string) outputFolder (fileEnding : string) content overwrite = + let mutable fileEnding = fileEnding + let withoutEnding = Path.GetFileNameWithoutExtension(fileId) + let mutable targetFile = Path.GetFullPath(Path.Combine(outputFolder, withoutEnding + fileEnding)) + + if (not overwrite) && _FileNamesGenerated.Contains(targetFile) then + let mutable enumeration = 1 + let pos = fileEnding.LastIndexOf('.') + let (beforeEnumeration, afterEnumeration) = + if pos = -1 + then "", fileEnding + else fileEnding.Substring(0, pos), fileEnding.Substring(pos) + while _FileNamesGenerated.Contains(targetFile) && enumeration < _EnumerationLimit do + fileEnding <- beforeEnumeration + enumeration.ToString() + afterEnumeration + targetFile <- Path.GetFullPath(Path.Combine(outputFolder, withoutEnding + fileEnding)) + enumeration <- enumeration + 1 + + _FileNamesGenerated.Add targetFile |> ignore + File.WriteAllText(targetFile, content) + interface IRewriteStep with member this.Name = "CSharpGeneration" @@ -34,19 +59,27 @@ type Emitter() = member this.Transformation (compilation, transformed) = let step = this :> IRewriteStep - let dir = step.AssemblyConstants.TryGetValue AssemblyConstants.OutputPath |> function - | true, outputFolder when outputFolder <> null -> Path.Combine(outputFolder, "src") - | _ -> step.Name + let dir = + step.AssemblyConstants.TryGetValue AssemblyConstants.OutputPath + |> function + | true, outputFolder when outputFolder <> null -> Path.Combine(outputFolder, "src") + | _ -> step.Name + |> (fun str -> (str.TrimEnd [| Path.DirectorySeparatorChar; Path.AltDirectorySeparatorChar |]) + Path.DirectorySeparatorChar.ToString() |> Uri) + |> (fun uri -> uri.LocalPath |> Path.GetDirectoryName) let context = CodegenContext.Create (compilation, step.AssemblyConstants) let allSources = GetSourceFiles.Apply compilation.Namespaces + + if (allSources.Count > 0 || not (compilation.EntryPoints.IsEmpty)) && not (Directory.Exists dir) then + Directory.CreateDirectory dir + |> ignore for source in allSources |> Seq.filter context.GenerateCodeForSource do let content = SimulationCode.generate source context - CompilationLoader.GeneratedFile(source, dir, ".g.cs", content) |> ignore + this.WriteFile source dir ".g.cs" content false for source in allSources |> Seq.filter (not << context.GenerateCodeForSource) do let content = SimulationCode.loadedViaTestNames source context - if content <> null then CompilationLoader.GeneratedFile(source, dir, ".dll.g.cs", content) |> ignore + this.WriteFile source dir ".dll.g.cs" content false if not compilation.EntryPoints.IsEmpty then @@ -60,11 +93,11 @@ type Emitter() = let mainSourceFile = (dir, "EntryPoint") |> Path.Combine |> Path.GetFullPath |> Uri |> CompilationUnitManager.GetFileId let content = EntryPoint.generateMainSource context entryPointCallables - CompilationLoader.GeneratedFile(mainSourceFile, dir, ".g.Main.cs", content) |> ignore + this.WriteFile mainSourceFile dir ".g.Main.cs" content false for (sourceFile, callables) in entryPointSources do let content = EntryPoint.generateSource context callables - CompilationLoader.GeneratedFile(sourceFile, dir, ".g.EntryPoint.cs", content) |> ignore + this.WriteFile sourceFile dir ".g.EntryPoint.cs" content false transformed <- compilation true