Собрать проект Visual Studio в один файл с помощью ILMerge

    app.exe, d1.dll d2.dll = app.exeОбычно результатом компиляции проекта является сборка (assembly) проекта, а также его зависимости (Referenced Assemblies). Однако иногда нужно чтобы результатом был один файл единственный файл, независимый от других сборок. Например простая утилита, которую можно будет куда угодно скопировать и она будет работать.

    Пример

    Условно говоря после:
        compile App\App.csproj  
        dir App\bin\Release  
    Получаются:
        App.exe
        dep1.dll
        dep2.dll
    Нам же нужно лишь один самодостаточный
        App.exe 
    То есть содержащий в себе dep1.dll и dep2.dll

    На Хабре уже присутствует решение со встраиванием зависимостей в ресурсы, здесь я покажу как это сделать с помощью ILMerge и Post Build Event в Visual Studio.


    Исходники
    Инструменты

    ILMerge — Программа от Microsoft Research, которая собственно и обладает требуемой функциональностью.
    merge_all.bat — им мы воспользуемся в Post-build event.

    Подготовка

    Скачать и установить ILMerge.
    Положить %PROGRAMFILES%\ILMerge\ILMerge.exe в папку ${SolutionDir}ILMerge\
    Там же создать файл merge_all.bat
    Добавить строчку в [Project->Properties->Build Events->Post-build event]:
    "$(SolutionDir)\ILMerge\merge_all.bat" "$(SolutionDir)" "$(TargetPath)" $(ConfigurationName)

    Содержание merge_all.bat

    Все сборки из $output будут сливаться в одну. Если конфигурация Debug то в $output\Output, если Release то в результате в $output будет только один файл. Информация как прошло слияние и какие были проблемы пишется в Visual Studio Output. Комментарии объясняют, что происходит внутри. Например в данном случае происходит выбор платформы .NET 4.

    @ECHO OFF
    
    rem #    set .NET version and output folder name
    set net="v4, C:\Windows\Microsoft.NET\Framework\v4.0.30319"
    set output=Output
    
    rem #    process arguments
    set ILMergeSolution=%1ILMerge\ILMerge.exe
    
    rem # determine programm files of x86 for 32 and 64 Platform
    IF     EXIST "%PROGRAMFILES(x86)%" set prorgammFiles=%PROGRAMFILES(x86)%
    IF NOT EXIST "%PROGRAMFILES(x86)%" set prorgammFiles=%PROGRAMFILES%
    
    rem #	if ILMerge.exe not in the $(SolutionDir)ILMerge\
    rem #		then try to use installed in prorgammFiles
    IF     EXIST %ILMergeSolution% set ILMerge="%ILMergeSolution%"
    IF NOT EXIST %ILMergeSolution% set ILMerge=%prorgammFiles%\Microsoft\ILMerge\ILMerge.exe
    
    set target_path=%2
    set target_file=%~nx2
    set target_dir=%~dp2
    set ConfigurationName=%3
    
    rem #    set output path and result file path
    set outdir=%target_dir%%output%
    set result=%outdir%\%target_file%
    
    rem #    print info
    @echo     Start %ConfigurationName% Merging %target_file%. 
    @echo Target: %target_path%
    @echo target_dir: %target_dir%
    @echo Config: %ConfigurationName% 
    
    rem #    recreate outdir
    IF EXIST "%outdir%" rmdir /S /Q "%outdir%"
    md "%outdir%"
    
    rem #    run merge cmd
    @echo Merging: '"%ILMerge%" /wildcards /targetplatform:%net% /out:"%result%" %target_path% "%target_dir%*.dll"'
    "%ILMerge%" /wildcards /targetplatform:%net% /out:"%result%" %target_path% "%target_dir%*.dll"
    
    rem #    if succeded
    IF %ErrorLevel% EQU 0 (
        
        rem #    clear real output folder and put there result assembly
        IF %ConfigurationName%==Release (
    
            del  %target_dir%*.*
            del  %target_dir%*.dll
            del  %target_dir%*.pdb
            del  %target_dir%*.xml
            del  %target_dir%*.*
            
            copy %result% %target_dir%
            rmdir /S /Q %outdir%
            set result=%target_path% 
            @echo Result: %target_file% "->  %target_path%"
        ) ELSE (
           @echo Result: %target_file% "->  %result%" )
       
       set status=succeded
       set errlvl=0    
    ) ELSE (
        set status=failed 
        set errlvl=1
        )
    
    @echo Merge %status%
    exit %errlvl% 
    


    UPD:
    Лицензия

    С сайта ILMerge:
    Commercial use permitted:
    The language of ILMerge's license has raised many questions. In a nutshell: commercial use is permitted, redistribution is not. Read the license carefully for full details since I am not (nor do I wish to be!) a lawyer.

    То есть встраивать в процесс сборки можно, а распространять со своим продуктом — нельзя.

    Открытым остался вопрос про лиценции объединяемых dll.

    Минусы
    • beliakov: ILMerge память жрет как бегемот… На большом количестве сборок
    • braindamaged: игнорирует ключик, который заставляет её, в свою очередь, игнорировать сборки и неймспейсы, оказавшиеся в зависимостях.

    Схожие инструменты
    • aspnet_merge Для ASP.NET
    • NGen Создает локальный кэш с dll

    Материалы по теме

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 29

      –10
      Не совсем понял, есть ли возможность включения в сборку .net runtime и запуска приложение на ОС, где .net не установлен. Если такой возможности нет, то особой пользы от ILMerge не вижу.
        +3
        Польза в том, что все либы будут внутри. Если у вас небольшая утилита, то не придётся таскать всегда с собой разные либы.

        Также это полезно большим проектам, где 100500 dll-ок. После смерживания они(проекты) шевелятся заметно шустрее.
          +1
          Лучше при установке NGen'ом по этим сборкам пройтись, а не валить все в одну кучу. Обновляться потом печально будет.
            +2
            Вы не путайте устанавливаемую софтину, которая может нормально использовать NGen, и portable-утилиту, которую идеально распространять одним exeшником.
              +1
              Я в основном говорил о проекте со 100500 сборок.
              0
              Не знал про NGen,
              но он решает другую задачу (вернее бенифит про быструю загрузку 100500 dll'ок).
              Кстати хорошее объяснение как им пользоваться есть тут.
              +1
              а насколько быстрее? буду благодарен за какое-нибудь сравнение
              +4
              Весь .NET встроить не получиться. Но если нужна маленькая утилита и вы решили ее написать на .NET, но хотите чтобы она была в одном файле то тогда подойдет.
              Или если библиотеку собрать в один dll, а не таскать кучу зависимостей.
              +1
              Можно задать возможно глупый вопрос, а что происходит в момент запуска приложения и в момент вызова функции из библиотеки? Т.е. библиотеки сохраняют все свойства и просто находят в одном файле или они в какой-то момент экспортируются в отдельные файлы?
                0
                Я точно не знаю принцип работы ILMerge,
                но судя по dotPeek (Reflector от JetBrains)
                он сливает все namespace с классами в одну dll,
                видимо разрешая имена классов и вызовы функций в рамках одной dll.
                Соответсвенно:
                IMHO при загрузке этой dll, загружается все необходимое пространство имен.
                  +1
                  ILMerge может поломать вам Reflection, ибо если мержатся сборки с одинаковыми именами классов и неймспейсами (приватные бывает совпадают), их переименуют.
                  0
                  Хороша софтинка ILMerge, но при использовании её натолкнулся на неприятную, хоть и мелкую, багу: она игнорирует ключик, который заставляет её, в свою очередь, игнорировать сборки и неймспейсы, оказавшиеся в зависимостях. Проблему обошли, в итоге, но время потратили.
                    +2
                    это который [/keyfile:filename [/delaysign]]?
                    как обошли?
                    0
                    Ниасилил EULA этой утилиты. Ее вообще можно в коммерческих целях применять?
                      +1
                      С сайта ILMerge:
                      Commercial use permitted:
                      The language of ILMerge's license has raised many questions. In a nutshell: commercial use is permitted, redistribution is not. Read the license carefully for full details since I am not (nor do I wish to be!) a lawyer.


                      То есть встраивать в процесс сборки можно, а распространять со своим продуктом — нельзя.
                        0
                        Ok, с этим понятно. Другой спорный момент. Лицензии сторонних компонент часто содержат запрет на декомпиляцию. А ILMerge похоже занимается именно этим — декомпилирует сборки и заново собирает в один файл, так?
                          +1
                          Декомпиляции не происходит, IL-код патчится на лету без преобразования его обратно в C#/VB.NET/etc. Чтобы понять, что именно происходит, стоит посмотреть на Mono.Cecil.
                      0
                      Может, конечно, спрошу глупость, но зачем нужен пункт
                      «Положить %PROGRAMFILES%\ILMerge\ILMerge.exe в папку ${SolutionDir}ILMerge\»
                      Неужели оно не будет работать (или будет криво работать) если оставить где есть, а в PATH дописать что там искать тоже, ну и в батнике написать ILMerge.exe без всяких абсолютных путей?
                        +2
                        Если в PATH то будет.

                        Но в мою задачу входило придумать решение для source control,
                        так чтобы если солюшн скачивается из source control на очередной машине,
                        то нет необходимости лезть в PATH. Нажал build и все.
                        Правда ILMerge.exe и merge_all.bat надо еще в директорию солюшена закинуть в VS.

                        Просто ILMerge.exe не будет, тк текущая папка это папка проекта.
                        +1
                        есть ещё aspnet_merge которая работает через ILMerge
                          0
                          ILMerge память жрет как бегемот… На большом количестве сборок вылетает с System.OutOfMemoryException.
                            +1
                            на 100500? (;
                              0
                              Нет, всего сотни хватило :)
                            +1
                            Вот мой выход. Можно было завернуть в отдельный подгужаемый таск-файл, но я не стал заморачивтся — нужно было в одном месте.

                             <Target Name="AfterBuild">
                              <ItemGroup>
                               <OutputFiles Include="$(TargetDir)\*.dll" />
                              </ItemGroup>
                              <PropertyGroup>
                               <BinOutput>$(SolutionDir)..\bin\$(Configuration)</BinOutput>
                              </PropertyGroup>
                              <MakeDir Directories="$(BinOutput)" />
                              <Exec WorkingDirectory="$(TargetDir)" Command="$(SolutionDir)\..\Alien\ILMerge.exe /target:library /out:$(BinOutput)\$(TargetFileName).dll /targetplatform:v4,$(MSBuildBinPath) @(OutputFiles, ' ')" />
                             </Target>

                            * This source code was highlighted with Source Code Highlighter.
                              +1
                              Спасибо за пример. До не мог понять как подступиться к ms build.
                              я взял его за базу и добавив экранирования путей закончил следующим:
                               <!-- ILMerge Step -->
                               <Target Name="AfterBuild">
                                <PropertyGroup >
                                  <ILMerge_Solution>$(SolutionDir)ILMerge\ILMerge.exe</ILMerge_Solution>
                                  <Programfiles>C:\Program Files (x86)\</Programfiles>
                                  <Programfiles Condition="!Exists('$(Programfiles)')">C:\Program Files\</Programfiles>
                                  <ILMerge_Installed>$(Programfiles)Microsoft\ILMerge\ILMerge.exe</ILMerge_Installed>
                                  <ErrorMesssage>ILMerge is not found here: '$(ILMerge_Solution)', nor here: '$(ILMerge_Installed)'</ErrorMesssage>

                                  <ILMerge>NOTSET</ILMerge>
                                  <ILMerge Condition="Exists('$(ILMerge_Installed)')">"$(ILMerge_Installed)"</ILMerge>
                                  <ILMerge Condition="Exists('$(ILMerge_Solution)')">"$(SolutionILMerge)"</ILMerge>
                                </PropertyGroup>
                                <Error Condition=" '$(ILMerge)' == 'NOTSET' " Text="Please Install ILMerge. $(ErrorMesssage)" />
                                
                                <ItemGroup>
                                 <OutputDlls Include="$(TargetDir)*.dll" />
                                 <OutputExes Include="$(TargetDir)*.exe" />
                                </ItemGroup>
                                <PropertyGroup>
                                 <BinOutput>$(TargetDir)merged\</BinOutput>
                                 <TargetFilePath>"$(BinOutput)$(TargetFileName)"</TargetFilePath>
                                 <OutputFiles>@(OutputExes->'"%(identity)"', ' ') @(OutputDlls->'"%(identity)"', ' ') </OutputFiles>
                                </PropertyGroup>
                                <MakeDir Directories="$(BinOutput)" />
                                <Exec WorkingDirectory="$(TargetDir)" Command='$(ILMerge) /out:$(TargetFilePath) $(OutputFiles) /targetplatform:v4,$(MSBuildBinPath) ' />
                               </Target>


                              * This source code was highlighted with Source Code Highlighter.
                                +1
                                Мелкий штрих: MsBuild подцепляет системные переменные как свои, то-есть нет необходимости объявлять Programfiles явно: в любом случае будут доступны $(ProgramFiles), и, если есть, $(ProgramFiles(x86)). Вам же нужен всегда 32-битный Program Files, в MsBuild есть специальная зарезервированая переменная для этого: $(MSBuildProgramFiles32).

                                Ну и для полной красоты, чтобы не мешатся с AfterBuild-ами, можно поместить этот таржет в отдельний файл, присвоить ему (таржету) собственное имя, по соглашению начинающееся с подчеркивания, и перегрузить переменную BuildDependsOn:
                                <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
                                 <PropertyGroup>
                                  <BuildDependsOn>
                                   $(BuildDependsOn);_IlMergeOutput
                                  </BuildDependsOn>
                                 </PropertyGroup>
                                 <Target Name="_IlMergeOutput">
                                  ...
                                 </Target>
                                </Project>

                                * This source code was highlighted with Source Code Highlighter.
                                Как-то так. Тогда в результирующий *.*proj файл достаточно будет добавить одну строку импорта, а AfterBuild-ы можно будет оставить для других использований.
                                  0
                                  Отлично, не знал $(MSBuildProgramFiles32).
                                  Кстати судя по ссылке она называется $(MSBuildExtensionsPath32).

                                  За завершающий штрих спасибо, так уже будет готовое решение.

                                  Не вы не знаете есть ли в переменных среды windows что-то типа %MSBuildExtensionsPath32%?
                                    0
                                    Extensions это не то. По ссылке нужно было смотреть коментарий :)
                                    Среди переменных windows вроде ничего похожего не встречал.
                              0
                              Если вы используете .NET 4, то лучше пойти другим путем. Мы в своем проекте нахлебались от ILMerge, после перехода на AppDomain.CurrentDomain.AssemblyResolve — никаких проблем. И главное — никаких EULA.

                              Only users with full accounts can post comments. Log in, please.