The Crystal Programming Language Forum

Get status of runned code into a crystal file

Hello, I ask you because actually, I’m front of a problem with my code.

Actually, when I install a softwre with my program, it run a Process.run(“crystal”, args: [“file.cr”]).

My problem is, I need to get the status of the runned code inside this file, and not the status of my Process.run.

My code (I just link the relevant part):

if userAgreement
                            matchingSoftwaresArray.each do |software|
                                puts ""

                                file = File.open("ISM.task", "w")
                                file << "require \"./#{ISM::Default::Path::SoftwaresDirectory + software.port + "/" + software.name + "/" + software.version + "/" + software.version + ".cr"}\"\n"
                                file << "target = Target.new\n"
                                file << "target.download\n"
                                file << "target.check\n"
                                file << "target.extract\n"
                                file << "target.patch\n"
                                file << "target.prepare\n"
                                file << "target.configure\n"
                                file << "target.build\n"
                                file << "target.install\n"
                                file << "target.clean\n"
                                file.close

                                Process.run("crystal",args: ["ISM.task"],output: :inherit)

                            end
                        end

Or have you got any suggestion to avoid this problem or improve ?

But I think I can’t avoid to use the crystal interpreter, because the required file in the generated file it’s a script containing the recipe to how to make the software

Is it possible for example inside my generated file to force the running file to return an error code when something in it code is wrong ?

Can you clarify this, please? Do you want your ISM.task file to return a status code, which you’d then get from your Process.run, or is it something else you want?

Yes it’s what I want

Try this:

if userAgreement
  matchingSoftwaresArray.each do |software|
    puts ""

    file_contents = <<-CODE
      require "./#{ISM::Default::Path::SoftwaresDirectory + software.port + "/" + software.name + "/" + software.version + "/" + software.version + ".cr"}"
      target = Target.new
      target.download
      target.check
      target.extract
      target.patch
      target.prepare
      target.configure
      target.build
      target.install
      target.clean
      exit 1 if <some condition>
      CODE

    File.write("ISM.task", file_contents)

    return_code = Process.run("crystal run",args: ["ISM.task"], output: :inherit)
  end
end

Here’s what’s going on:

  1. I’m using a heredoc string (see string literal docs) to simplify your file writing into simply creating the file contents and then writing it to a file.
  2. Inside the Crystal file you’re writing, I add that you exit with return code 1 if some condition is fulfilled. It’s not clear to me when you’d want to return a non-zero error code from the particular code you shared, so I just put a space for you to fill in.
  3. Process.run returns the return code of the process, so we assign a variable to that value.

A couple other notes:

  • I used crystal run rather than just crystal because it makes it more clear what you’re trying to do.
  • In the future, I recommend using the File.open method that takes a block instead of creating a new file. That way you don’t have to worry about closing the file manually. While I prefer the heredoc approach in this particular case, your previous file writing code would look like this if you used the block method:
File.open("ISM.task", "w") do |file|
  file << "require \"./#{ISM::Default::Path::SoftwaresDirectory + software.port + "/" + software.name + "/" + software.version + "/" + software.version + ".cr"}\"\n"
  file << "target = Target.new\n"
  file << "target.download\n"
  file << "target.check\n"
  file << "target.extract\n"
  file << "target.patch\n"
  file << "target.prepare\n"
  file << "target.configure\n"
  file << "target.build\n"
  file << "target.install\n"
  file << "target.clean\n"
end

Hey, thanks you very much, you gave me a lot of usefull things in this reply !

I will come back to you if I need more help, I will try that. Thanks you

1 Like

Just one question as well, is it normal when I call target.download in this generated file, I don’t have the output of wget when it download a file ?

I call wget like this:

Process.run("wget", args: [@information.downloadLinks[0]],
                                output: :inherit,
                                chdir: Ism.settings.sourcesPath+"/"+@information.versionName)

wget outputs to stderr. See this StackOverflow question for an explanation of why. You can handle this easily by inheriting the error stream as well:

Process.run("wget", args: [@information.downloadLinks[0]],
                                output: :inherit,
                                error: :inherit,
                                chdir: Ism.settings.sourcesPath+"/"+@information.versionName)

One question, but you solved almost all of my problem.

Now I just need for this block:

file_contents = <<-CODE
      require "./#{ISM::Default::Path::SoftwaresDirectory + software.port + "/" + software.name + "/" + software.version + "/" + software.version + ".cr"}"
      target = Target.new
      target.download
      target.check
      target.extract
      target.patch
      target.prepare
      target.configure
      target.build
      target.install
      target.clean
      exit 1 if <some condition>
      CODE

I need to exit 1 when all of target.something failed. Because actually my file execute all commands without checking if one of them failed.

This seems like a good use case for exceptions. You could raise exceptions with appropriate messages in fail cases for each target#something and then do something like

file_contents = <<-CODE
  require "./#{ISM::Default::Path::SoftwaresDirectory + software.port + "/" + software.name + "/" + software.version + "/" + software.version + ".cr"}"
  target = Target.new
  begin
    target.download
    target.check
    target.extract
    target.patch
    target.prepare
    target.configure
    target.build
    target.install
    target.clean
  rescue ex
    STDERR.puts "Error: #{ex.message}"
    exit 1 if <some condition>
  end
  CODE

Hey thanks you !

Now I have one last problem.

My target class inherit of this class:

module ISM

    class Software

        property information : ISM::SoftwareInformation
        property mainSourceDirectoryName : String

        def initialize(informationPath : String)
            @information = ISM::SoftwareInformation.new
            @information.loadInformationFile(informationPath)
            @mainSourceDirectoryName = String.new
        end

        def download
            Dir.mkdir(Ism.settings.sourcesPath+"/"+@information.versionName)
            Ism.notifyOfDownload(@information)
        end

        def downloadSource(link : String)
            if !Process.run("wget", args: [link],
                output: :inherit,
                error: :inherit,
                chdir: Ism.settings.sourcesPath+"/"+@information.versionName).success?
                Ism.notifyOfDownloadError(link)
                exit 1
            end
        end
        
        def check
            Ism.notifyOfCheck(@information)
        end
        
        def extract
            Ism.notifyOfExtract(@information)
        end

        def extractSource(archive : String)
            if !Process.run("tar",  args: ["-xf", archive],
                                    chdir: Ism.settings.sourcesPath+"/"+@information.versionName).success?
                Ism.notifyOfExtractError(archive)
                exit 1
            end
        end
        
        def patch
            Ism.notifyOfPatch(@information)
        end
        
        def applyPatch(patch : String)
            if !Process.run("patch",    args: ["-Np1","-i",patch],
                                        chdir: Ism.settings.sourcesPath+"/"+@information.versionName+"/"+@mainSourceDirectoryName).success?
                Ism.notifyOfApplyPatchError(patch)
                exit 1
            end
        end

        def prepare
            Ism.notifyOfPrepare(@information)
        end

        def moveFile(path : String, newPath : String)
            FileUtils.mv(   Ism.settings.sourcesPath + "/" + 
                            @information.versionName + "/" +
                            path,
                            Ism.settings.sourcesPath + "/" + 
                            @information.versionName + "/" +
                            newPath)
        end

        def makeDirectory(directory : String)
            Dir.mkdir(  Ism.settings.sourcesPath + "/" + 
                        @information.versionName + "/" +
                        directory)
        end
        
        def configure
            Ism.notifyOfConfigure(@information)
        end

        def configureSource(arguments : Array(String), path = String.new)
            if !Process.run("./configure", args: arguments,
                                            output: :inherit,
                                            error: :inherit,
                                            chdir:  Ism.settings.sourcesPath + "/" + 
                                                    @information.versionName + "/" +
                                                    @mainSourceDirectoryName + "/" +
                                                    path)
                Ism.notifyOfConfigureError(path)
                exit 1
            end
        end
        
        def build
            Ism.notifyOfBuild(@information)
        end

        def makeSource(arguments : Array(String), path = String.new)
            if !Process.run("make", args: arguments,
                                    output: :inherit,
                                    error: :inherit,
                                    chdir:  Ism.settings.sourcesPath + "/" + 
                                            @information.versionName + "/" +
                                            @mainSourceDirectoryName + "/" +
                                            path)
                Ism.notifyOfMakeError(path)
                exit 1
            end
        end
        
        def install
            Ism.notifyOfInstall(@information)
        end
        
        def clean
            Ism.notifyOfClean(@information)
            FileUtils.rm_r(Ism.settings.sourcesPath+"/"+@information.versionName)
        end

        def uninstall
            Ism.notifyOfUninstall(@information)
        end

        def option?(optionName : String) : Bool
            result = false
            @information.options.each do |option|
                if optionName == option.name
                    result = option.active
                end
            end
            return result
        end

    end

end

But when I have an error, and just when I have an error, target.something don’t show my error message, why ?

I added some missing .success? tests, but this didn’t solved the problem

I solved my problem, it’s because I need to avoid :

if !Process.run("wget", args: [link],
                output: :inherit,
                error: :inherit,
                chdir: Ism.settings.sourcesPath+"/"+@information.versionName).success?
                Ism.notifyOfDownloadError(link)
                exit 1
            end

and replace to this:

process = Process.run("wget",   args: [link],
                                            output: :inherit,
                                            error: :inherit,
                                            chdir: Ism.settings.sourcesPath+"/"+@information.versionName)
            if !process.success?
                Ism.notifyOfDownloadError(link)
                exit 1
            end
1 Like