Skip to content

Commit

Permalink
Merge pull request #181 from PublicApiGenerator/api-on-stdout
Browse files Browse the repository at this point in the history
Generate API on STDOUT when single target framework given
  • Loading branch information
danielmarbach authored Jan 31, 2020
2 parents 20e712b + 0b1afc0 commit b29bfcb
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 31 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ Generate public API for fluent assertions 5.* for runtime framework `net47`
generate-public-api --target-frameworks net47 --package FluentAssertions --package-version 5.*
```

Note that when a single target framework is specified then the API is generated to standard output. To write to a file, you can either use shell redirection:

```
generate-public-api --target-frameworks net47 --package FluentAssertions --package-version 5.* > api.txt
```

or specify an output directory to force the generation of an API file:

```
generate-public-api --target-frameworks net47 --package FluentAssertions --package-version 5.* --output-directory .
```

Generate public API for fluent assertions 5.6.0 (exact version match) for runtime framework `net47`

```
Expand All @@ -161,6 +173,8 @@ The target framework in which the package will be restored. The target framework

It is not possible to use `netstandard2.0` because it is not a valid runtime framework.

If only a single target framework is given then the API is generated to the standard output unless the `--output-directory` option is also specified.

```
--package-name PackageName
```
Expand Down
106 changes: 85 additions & 21 deletions src/PublicApiGenerator.Tool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace PublicApiGenerator.Tool
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
Expand All @@ -29,7 +30,9 @@ public static class Program
/// <returns></returns>
static int Main(string targetFrameworks, string? assembly = null, string? projectPath = null, string? package = null, string? packageVersion = null, string? generatorVersion = null, string? workingDirectory = null, string? outputDirectory = null, bool verbose = false, bool leaveArtifacts = false)
{
if (string.IsNullOrEmpty(outputDirectory))
var frameworks = targetFrameworks.Split(";");

if (string.IsNullOrEmpty(outputDirectory) && frameworks.Length > 1)
{
outputDirectory = Environment.CurrentDirectory;
}
Expand All @@ -55,7 +58,7 @@ static int Main(string targetFrameworks, string? assembly = null, string? projec

SaveProject(workingArea, project, logVerbose);

foreach (var framework in targetFrameworks.Split(";"))
foreach (var framework in frameworks)
{
GeneratePublicApi(assembly, package, workingArea, framework, outputDirectory, logVerbose, logError);
}
Expand Down Expand Up @@ -86,51 +89,109 @@ private static void GeneratePublicApi(string? assembly, string? package, string
var relativePath = Path.Combine(workingArea, "bin", "Release", framework);
var name = !string.IsNullOrEmpty(assembly) ? $"{assembly}" : $"{package}.dll";
relativePath = Path.Combine(relativePath, name);
var fullPath = Path.GetFullPath(relativePath);
var outputPath = Path.Combine(Path.GetDirectoryName(relativePath), $"{Path.GetFileNameWithoutExtension(name)}.{framework}.received.txt");
var assemblyPath = Path.GetFullPath(relativePath);

var apiFilePath = outputDirectory != null
? Path.Combine(workingArea, $"{Path.GetFileNameWithoutExtension(name)}.{framework}.received.txt")
: null;

try
{
// Because we run in different appdomain we can always unload
RunDotnet(workingArea, $"run --configuration Release --framework {framework} -- {fullPath} {outputPath} {outputDirectory}", logVerbose);

logVerbose.WriteLine($"Public API file: {outputPath}");
logVerbose.WriteLine();
RunDotnet(workingArea, logVerbose,
apiFilePath != null ? null : Console.Out,
"run",
"--configuration", "Release",
"--framework", framework,
"--",
assemblyPath, apiFilePath ?? "-");
}
catch (FileNotFoundException)
{
logError.WriteLine($"Unable to find {fullPath}. Consider specifying --assembly");
logError.WriteLine($"Unable to find {assemblyPath}. Consider specifying --assembly");
throw;
}
}

private static void RunDotnet(string workingArea, string arguments, TextWriter logVerbose)
{
logVerbose.WriteLine($"Dotnet arguments: {arguments}");
if (outputDirectory == null || apiFilePath == null)
{
return;
}

logVerbose.WriteLine($"Public API file: {apiFilePath}");
logVerbose.WriteLine();

var destinationFilePath = Path.Combine(outputDirectory, Path.GetFileName(apiFilePath));

if (File.Exists(destinationFilePath))
File.Delete(destinationFilePath);
File.Move(apiFilePath, destinationFilePath);

Console.WriteLine(Path.GetFullPath(destinationFilePath));
}

private static void RunDotnet(string workingArea, TextWriter logVerbose, TextWriter? stdout, params string[] arguments)
{
var psi = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = arguments,
WorkingDirectory = workingArea,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var process = Process.Start(psi);
process.WaitForExit(10000);

var output = process.StandardOutput.ReadToEnd();

logVerbose.WriteLine($"Dotnet output: {output}");
logVerbose.WriteLine($"Invoking dotnet with arguments:");
foreach (var (i, arg) in arguments.Select((arg, i) => (i + 1, arg)))
{
logVerbose.WriteLine($"{i,4}. {arg}");
psi.ArgumentList.Add(arg);
}
logVerbose.WriteLine();

using var process = Process.Start(psi);

if (stdout == null)
{
logVerbose.WriteLine("Dotnet output:");
}

const string indent = " ";
process.OutputDataReceived += DataReceivedEventHandler(stdout ?? logVerbose, stdout == null ? indent : null);
process.ErrorDataReceived += DataReceivedEventHandler(logVerbose, indent);

process.BeginOutputReadLine();
process.BeginErrorReadLine();

if (stdout == null)
{
logVerbose.WriteLine();
}

if (!process.WaitForExit(10000))
{
throw new TimeoutException($"Process \"{psi.FileName}\" ({process.Id}) took too long to run.");
}

if (process.ExitCode != 0)
{
var error = process.StandardError.ReadToEnd();
var pseudoCommandLine = string.Join(" ", from arg in arguments
select arg.IndexOf('"') >= 0
? $"\"{arg.Replace("\"", "\"\"")}\""
: arg);
throw new Exception(
$"dotnet exit code {process.ExitCode}. Directory: {workingArea}. Args: {arguments}. Output: {output}. Error: {error}");
$"dotnet exit code {process.ExitCode}. Directory: {workingArea}. Args: {pseudoCommandLine}.");
}

static DataReceivedEventHandler
DataReceivedEventHandler(TextWriter writer, string? prefix = null) =>
(_, args) =>
{
if (args.Data == null)
{
return; // EOI
}
writer.WriteLine(prefix + args.Data);
};
}

private static void SaveProject(string workingArea, XElement project, TextWriter logVerbose)
Expand Down Expand Up @@ -164,7 +225,10 @@ private static string GetManifestResourceText(this Type type, string name,
{
using var stream = type.Assembly.GetManifestResourceStream(type, name);
if (stream == null)
{
throw new Exception($"Resource named \"{type.Namespace}.{name}\" not found.");
}

using var reader = encoding == null ? new StreamReader(stream)
: new StreamReader(stream, encoding);
return reader.ReadToEnd();
Expand Down
25 changes: 15 additions & 10 deletions src/PublicApiGenerator.Tool/SubProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@

static class Program
{
static void Main(string[] args)
static int Main(string[] args)
{
var fullPath = args[0];
var outputPath = args[1];
var outputDirectory = args[2];
var asm = Assembly.LoadFile(fullPath);
File.WriteAllText(outputPath, asm.GeneratePublicApi());
var destinationFilePath = Path.Combine(outputDirectory, Path.GetFileName(outputPath));
if (File.Exists(destinationFilePath))
try
{
File.Delete(destinationFilePath);
var assemblyPath = args[0];
var asm = Assembly.LoadFile(assemblyPath);
switch (args[1])
{
case "-": Console.WriteLine(asm.GeneratePublicApi()); break;
case string apiFilePath: File.WriteAllText(apiFilePath, asm.GeneratePublicApi()); break;
}
return 0;
}
catch (Exception e)
{
Console.Error.WriteLine(e);
return 1;
}
File.Move(outputPath, destinationFilePath);
}
}

0 comments on commit b29bfcb

Please sign in to comment.