All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.plexus.compiler.csharp.CSharpCompiler Maven / Gradle / Ivy

package org.codehaus.plexus.compiler.csharp;

/*
 * Copyright 2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import javax.inject.Named;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.codehaus.plexus.compiler.AbstractCompiler;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.CompilerException;
import org.codehaus.plexus.compiler.CompilerMessage;
import org.codehaus.plexus.compiler.CompilerOutputStyle;
import org.codehaus.plexus.compiler.CompilerResult;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
import org.codehaus.plexus.util.cli.WriterStreamConsumer;

/**
 * @author Gilles Dodinet
 * @author Trygve Laugstøl
 * @author Matthew Pocock
 * @author Chris Stevenson
 */
@Named("csharp")
public class CSharpCompiler extends AbstractCompiler {
    private static final String JAR_SUFFIX = ".jar";
    private static final String DLL_SUFFIX = ".dll";
    private static final String NET_SUFFIX = ".net";

    private static final String ARGUMENTS_FILE_NAME = "csharp-arguments";

    private static final String[] DEFAULT_INCLUDES = {"**/**"};

    private Map compilerArguments;

    // ----------------------------------------------------------------------
    //
    // ----------------------------------------------------------------------

    public CSharpCompiler() {
        super(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES, ".cs", null, null);
    }

    // ----------------------------------------------------------------------
    // Compiler Implementation
    // ----------------------------------------------------------------------

    @Override
    public String getCompilerId() {
        return "csharp";
    }

    public boolean canUpdateTarget(CompilerConfiguration configuration) throws CompilerException {
        return false;
    }

    public String getOutputFile(CompilerConfiguration configuration) throws CompilerException {
        return configuration.getOutputFileName() + "." + getTypeExtension(configuration);
    }

    public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
        File destinationDir = new File(config.getOutputLocation());

        if (!destinationDir.exists()) {
            destinationDir.mkdirs();
        }

        config.setSourceFiles(null);

        String[] sourceFiles = CSharpCompiler.getSourceFiles(config);

        if (sourceFiles.length == 0) {
            return new CompilerResult().success(true);
        }

        logCompiling(sourceFiles, config);

        String[] args = buildCompilerArguments(config, sourceFiles);

        List messages;

        if (config.isFork()) {
            messages = compileOutOfProcess(
                    config.getWorkingDirectory(), config.getBuildDirectory(), findExecutable(config), args);
        } else {
            throw new CompilerException("This compiler doesn't support in-process compilation.");
        }

        return new CompilerResult().compilerMessages(messages);
    }

    public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
        return buildCompilerArguments(config, CSharpCompiler.getSourceFiles(config));
    }

    // ----------------------------------------------------------------------
    //
    // ----------------------------------------------------------------------

    private Map getCompilerArguments(CompilerConfiguration config) {
        if (compilerArguments != null) {
            return compilerArguments;
        }

        compilerArguments = config.getCustomCompilerArgumentsAsMap();

        Iterator i = compilerArguments.keySet().iterator();

        while (i.hasNext()) {
            String orig = i.next();
            String v = compilerArguments.get(orig);
            if (orig.contains(":") && v == null) {
                String[] arr = orig.split(":");
                i.remove();
                String k = arr[0];
                v = arr[1];
                compilerArguments.put(k, v);
                if (config.isDebug()) {
                    System.out.println("transforming argument from " + orig + " to " + k + " = [" + v + "]");
                }
            }
        }

        config.setCustomCompilerArgumentsAsMap(compilerArguments);

        return compilerArguments;
    }

    private String findExecutable(CompilerConfiguration config) {
        String executable = config.getExecutable();

        if (!StringUtils.isEmpty(executable)) {
            return executable;
        }

        if (Os.isFamily("windows")) {
            return "csc";
        }

        return "mcs";
    }

    /*
    $ mcs --help
    Mono C# compiler, (C) 2001 - 2003 Ximian, Inc.
    mcs [options] source-files
       --about            About the Mono C# compiler
       -addmodule:MODULE  Adds the module to the generated assembly
       -checked[+|-]      Set default context to checked
       -codepage:ID       Sets code page to the one in ID (number, utf8, reset)
       -clscheck[+|-]     Disables CLS Compliance verifications
       -define:S1[;S2]    Defines one or more symbols (short: /d:)
       -debug[+|-], -g    Generate debugging information
       -delaysign[+|-]    Only insert the public key into the assembly (no signing)
       -doc:FILE          XML Documentation file to generate
       -keycontainer:NAME The key pair container used to strongname the assembly
       -keyfile:FILE      The strongname key file used to strongname the assembly
       -langversion:TEXT  Specifies language version modes: ISO-1 or Default
       -lib:PATH1,PATH2   Adds the paths to the assembly link path
       -main:class        Specified the class that contains the entry point
       -noconfig[+|-]     Disables implicit references to assemblies
       -nostdlib[+|-]     Does not load core libraries
       -nowarn:W1[,W2]    Disables one or more warnings
       -optimize[+|-]     Enables code optimalizations
       -out:FNAME         Specifies output file
       -pkg:P1[,Pn]       References packages P1..Pn
       -recurse:SPEC      Recursively compiles the files in SPEC ([dir]/file)
       -reference:ASS     References the specified assembly (-r:ASS)
       -target:KIND       Specifies the target (KIND is one of: exe, winexe,
                          library, module), (short: /t:)
       -unsafe[+|-]       Allows unsafe code
       -warnaserror[+|-]  Treat warnings as errors
       -warn:LEVEL        Sets warning level (the highest is 4, the default is 2)
       -help2             Show other help flags

    Resources:
       -linkresource:FILE[,ID] Links FILE as a resource
       -resource:FILE[,ID]     Embed FILE as a resource
       -win32res:FILE          Specifies Win32 resource file (.res)
       -win32icon:FILE         Use this icon for the output
       @file                   Read response file for more options

    Options can be of the form -option or /option
        */

    private String[] buildCompilerArguments(CompilerConfiguration config, String[] sourceFiles)
            throws CompilerException {
        List args = new ArrayList<>();

        if (config.isDebug()) {
            args.add("/debug+");
        } else {
            args.add("/debug-");
        }

        // config.isShowWarnings()
        // config.getSourceVersion()
        // config.getTargetVersion()
        // config.getSourceEncoding()

        // ----------------------------------------------------------------------
        //
        // ----------------------------------------------------------------------

        for (String element : config.getClasspathEntries()) {
            File f = new File(element);

            if (!f.isFile()) {
                continue;
            }

            if (element.endsWith(JAR_SUFFIX)) {
                try {
                    File dllDir = new File(element + NET_SUFFIX);
                    if (!dllDir.exists()) {
                        dllDir.mkdir();
                    }
                    JarUtil.extract(dllDir.toPath(), new File(element));
                    for (String tmpfile : dllDir.list()) {
                        if (tmpfile.endsWith(DLL_SUFFIX)) {
                            String dll =
                                    Paths.get(dllDir.getAbsolutePath(), tmpfile).toString();
                            args.add("/reference:\"" + dll + "\"");
                        }
                    }
                } catch (IOException e) {
                    throw new CompilerException(e.toString(), e);
                }
            } else {
                args.add("/reference:\"" + element + "\"");
            }
        }

        // ----------------------------------------------------------------------
        // Main class
        // ----------------------------------------------------------------------

        Map compilerArguments = getCompilerArguments(config);

        String mainClass = compilerArguments.get("-main");

        if (!StringUtils.isEmpty(mainClass)) {
            args.add("/main:" + mainClass);
        }

        // ----------------------------------------------------------------------
        // Xml Doc output
        // ----------------------------------------------------------------------

        String doc = compilerArguments.get("-doc");

        if (!StringUtils.isEmpty(doc)) {
            args.add("/doc:"
                    + new File(config.getOutputLocation(), config.getOutputFileName() + ".xml").getAbsolutePath());
        }

        // ----------------------------------------------------------------------
        // Xml Doc output
        // ----------------------------------------------------------------------

        String nowarn = compilerArguments.get("-nowarn");

        if (!StringUtils.isEmpty(nowarn)) {
            args.add("/nowarn:" + nowarn);
        }

        // ----------------------------------------------------------------------
        // Out - Override output name, this is required for generating the unit test dll
        // ----------------------------------------------------------------------

        String out = compilerArguments.get("-out");

        if (!StringUtils.isEmpty(out)) {
            args.add("/out:" + new File(config.getOutputLocation(), out).getAbsolutePath());
        } else {
            args.add("/out:" + new File(config.getOutputLocation(), getOutputFile(config)).getAbsolutePath());
        }

        // ----------------------------------------------------------------------
        // Resource File - compile in a resource file into the assembly being created
        // ----------------------------------------------------------------------
        String resourcefile = compilerArguments.get("-resourcefile");

        if (!StringUtils.isEmpty(resourcefile)) {
            String resourceTarget = compilerArguments.get("-resourcetarget");
            args.add("/res:" + new File(resourcefile).getAbsolutePath() + "," + resourceTarget);
        }

        // ----------------------------------------------------------------------
        // Target - type of assembly to produce, lib,exe,winexe etc...
        // ----------------------------------------------------------------------

        String target = compilerArguments.get("-target");

        if (StringUtils.isEmpty(target)) {
            args.add("/target:library");
        } else {
            args.add("/target:" + target);
        }

        // ----------------------------------------------------------------------
        // remove MS logo from output (not applicable for mono)
        // ----------------------------------------------------------------------
        String nologo = compilerArguments.get("-nologo");

        if (!StringUtils.isEmpty(nologo)) {
            args.add("/nologo");
        }

        // ----------------------------------------------------------------------
        // add any resource files
        // ----------------------------------------------------------------------
        this.addResourceArgs(config, args);

        // ----------------------------------------------------------------------
        // add source files
        // ----------------------------------------------------------------------
        for (String sourceFile : sourceFiles) {
            args.add(sourceFile);
        }

        return args.toArray(new String[args.size()]);
    }

    private void addResourceArgs(CompilerConfiguration config, List args) {
        File filteredResourceDir = this.findResourceDir(config);
        if ((filteredResourceDir != null) && filteredResourceDir.exists()) {
            DirectoryScanner scanner = new DirectoryScanner();
            scanner.setBasedir(filteredResourceDir);
            scanner.setIncludes(DEFAULT_INCLUDES);
            scanner.addDefaultExcludes();
            scanner.scan();

            List includedFiles = Arrays.asList(scanner.getIncludedFiles());
            for (String name : includedFiles) {
                File filteredResource = new File(filteredResourceDir, name);
                String assemblyResourceName = this.convertNameToAssemblyResourceName(name);
                String argLine = "/resource:\"" + filteredResource + "\",\"" + assemblyResourceName + "\"";
                if (config.isDebug()) {
                    System.out.println("adding resource arg line:" + argLine);
                }
                args.add(argLine);
            }
        }
    }

    private File findResourceDir(CompilerConfiguration config) {
        if (config.isDebug()) {
            System.out.println("Looking for resourcesDir");
        }

        Map compilerArguments = getCompilerArguments(config);

        String tempResourcesDirAsString = compilerArguments.get("-resourceDir");
        File filteredResourceDir = null;
        if (tempResourcesDirAsString != null) {
            filteredResourceDir = new File(tempResourcesDirAsString);
            if (config.isDebug()) {
                System.out.println("Found resourceDir at: " + filteredResourceDir.toString());
            }
        } else {
            if (config.isDebug()) {
                System.out.println("No resourceDir was available.");
            }
        }
        return filteredResourceDir;
    }

    private String convertNameToAssemblyResourceName(String name) {
        return name.replace(File.separatorChar, '.');
    }

    @SuppressWarnings("deprecation")
    private List compileOutOfProcess(
            File workingDirectory, File target, String executable, String[] args) throws CompilerException {
        // ----------------------------------------------------------------------
        // Build the @arguments file
        // ----------------------------------------------------------------------

        File file;

        PrintWriter output = null;

        try {
            file = new File(target, ARGUMENTS_FILE_NAME);

            output = new PrintWriter(new FileWriter(file));

            for (String arg : args) {
                output.println(arg);
            }
        } catch (IOException e) {
            throw new CompilerException("Error writing arguments file.", e);
        } finally {
            IOUtil.close(output);
        }

        // ----------------------------------------------------------------------
        // Execute!
        // ----------------------------------------------------------------------

        Commandline cli = new Commandline();

        cli.setWorkingDirectory(workingDirectory.getAbsolutePath());

        cli.setExecutable(executable);

        cli.createArgument().setValue("@" + file.getAbsolutePath());

        Writer stringWriter = new StringWriter();

        StreamConsumer out = new WriterStreamConsumer(stringWriter);

        StreamConsumer err = new WriterStreamConsumer(stringWriter);

        int returnCode;

        List messages;

        try {
            returnCode = CommandLineUtils.executeCommandLine(cli, out, err);

            messages = parseCompilerOutput(new BufferedReader(new StringReader(stringWriter.toString())));
        } catch (CommandLineException | IOException e) {
            throw new CompilerException("Error while executing the external compiler.", e);
        }

        if (returnCode != 0 && messages.isEmpty()) {
            // TODO: exception?
            messages.add(new CompilerMessage(
                    "Failure executing the compiler, but could not parse the error:" + EOL + stringWriter.toString(),
                    true));
        }

        return messages;
    }

    public static List parseCompilerOutput(BufferedReader bufferedReader) throws IOException {
        List messages = new ArrayList<>();

        String line = bufferedReader.readLine();

        while (line != null) {
            CompilerMessage compilerError = DefaultCSharpCompilerParser.parseLine(line);

            if (compilerError != null) {
                messages.add(compilerError);
            }

            line = bufferedReader.readLine();
        }

        return messages;
    }

    private String getType(Map compilerArguments) {
        String type = compilerArguments.get("-target");

        if (StringUtils.isEmpty(type)) {
            return "library";
        }

        return type;
    }

    private String getTypeExtension(CompilerConfiguration configuration) throws CompilerException {
        String type = getType(configuration.getCustomCompilerArgumentsAsMap());

        if ("exe".equals(type) || "winexe".equals(type)) {
            return "exe";
        }

        if ("library".equals(type) || "module".equals(type)) {
            return "dll";
        }

        throw new CompilerException("Unrecognized type '" + type + "'.");
    }

    // added for debug purposes....
    protected static String[] getSourceFiles(CompilerConfiguration config) {
        Set sources = new HashSet<>();

        // Set sourceFiles = null;
        // was:
        Set sourceFiles = config.getSourceFiles();

        if (sourceFiles != null && !sourceFiles.isEmpty()) {
            for (File sourceFile : sourceFiles) {
                sources.add(sourceFile.getAbsolutePath());
            }
        } else {
            for (String sourceLocation : config.getSourceLocations()) {
                if (!new File(sourceLocation).exists()) {
                    if (config.isDebug()) {
                        System.out.println("Ignoring not found sourceLocation at: " + sourceLocation);
                    }
                    continue;
                }
                sources.addAll(getSourceFilesForSourceRoot(config, sourceLocation));
            }
        }

        String[] result;

        if (sources.isEmpty()) {
            result = new String[0];
        } else {
            result = sources.toArray(new String[sources.size()]);
        }

        return result;
    }

    protected static Set getSourceFilesForSourceRoot(CompilerConfiguration config, String sourceLocation) {
        DirectoryScanner scanner = new DirectoryScanner();

        scanner.setBasedir(sourceLocation);

        Set includes = config.getIncludes();

        if (includes != null && !includes.isEmpty()) {
            String[] inclStrs = includes.toArray(new String[includes.size()]);
            scanner.setIncludes(inclStrs);
        } else {
            scanner.setIncludes(new String[] {"**/*.cs"});
        }

        Set excludes = config.getExcludes();

        if (excludes != null && !excludes.isEmpty()) {
            String[] exclStrs = excludes.toArray(new String[excludes.size()]);
            scanner.setIncludes(exclStrs);
        }

        scanner.scan();

        String[] sourceDirectorySources = scanner.getIncludedFiles();

        Set sources = new HashSet<>();

        for (String source : sourceDirectorySources) {
            File f = new File(sourceLocation, source);

            sources.add(f.getPath());
        }

        return sources;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy